iOS 降低线上版本Crash率

#iOS 降低线上版本Crash率

IOS 防止Crash 组件WTSafeGuard

##背景
由于Object-C本身的不安全性,导致很容易产生Crash。在这些Crash,很多我们可以利用自定义手段,进行避免。这样可以降低线上版本的Crash率,提升用户
体验。WTSafeGuard 避免APP Crash 组件,目前能做到的还很有限。

UIKit Called on Non-Main Thread

UIKit不是线程安全的,执行UIKit操作如果不在主线程很可能造成程序Crash。所以我们对Hook,UIView 的setNeedsLayout,layoutIfNeeded,layoutSubviews,setNeedsUpdateConstraints方法。如果执行以上函数没有在主队列,通过强行将执行代码,在主队列执行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
- (void)wt_safe_setNeedsLayout
{
if(![NSThread isMainThread]){
dispatch_async(dispatch_get_main_queue(), ^{
NSAssert(false, @"wt_safe_setNeedsLayout failed");
[self wt_safe_setNeedsLayout];
});
}else{
[self wt_safe_setNeedsLayout];
}
}

- (void)wt_safe_layoutIfNeeded
{
if(![NSThread isMainThread]){
dispatch_async(dispatch_get_main_queue(), ^{
NSAssert(false, @"wt_safe_layoutIfNeeded failed");
[self wt_safe_layoutIfNeeded];
});
}else{
[self wt_safe_layoutIfNeeded];
}
}

- (void)wt_safe_layoutSubviews
{
if(![NSThread isMainThread]){
dispatch_async(dispatch_get_main_queue(), ^{
NSAssert(false, @"wt_safe_layoutSubviews failed");
[self wt_safe_layoutSubviews];
});
}else{
[self wt_safe_layoutSubviews];
}
}

- (void)wt_safe_setNeedsUpdateConstraints
{
if(![NSThread isMainThread]){
dispatch_async(dispatch_get_main_queue(), ^{
NSAssert(false, @"wt_safe_setNeedsUpdateConstraints failed");
[self wt_safe_setNeedsUpdateConstraints];
});
}else{
[self wt_safe_setNeedsUpdateConstraints];
}
}

##避免 Foundation 类Carsh

###NSString

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
+ (instancetype)stringWithUTF8String:(const char *)bytes
- (instancetype)initWithString:(NSString *)aString
- (instancetype)initWithUTF8String:(const char *)nullTerminatedCString
- (instancetype)initWithFormat:(NSString *)format locale:(id)locale arguments:(va_list)argList
- (NSString *)stringByAppendingString:(NSString *)aString
- (unichar)characterAtIndex:(NSUInteger)index
- (void)getCharacters:(unichar *)buffer range:(NSRange)range
- (NSRange)rangeOfCharacterFromSet:(NSCharacterSet *)searchSet
options:(NSStringCompareOptions)mask
range:(NSRange)searchRange

- (NSRange)rangeOfString:(NSString *)searchString
options:(NSStringCompareOptions)mask
range:(NSRange)searchRange
locale:(NSLocale *)locale
- (NSString *)substringFromIndex:(NSUInteger)from
- (NSString *)substringWithRange:(NSRange)range
- (NSString *)substringToIndex:(NSUInteger)to
- (void)getLineStart:(NSUInteger *)startPtr
end:(NSUInteger *)lineEndPtr
contentsEnd:(NSUInteger *)contentsEndPtr
forRange:(NSRange)range

NSAttributedString

hook 方法:对传入参数range 进行check,如果range有问题,直接返回nil

1
- (NSAttributedString *)attributedSubstringFromRange:(NSRange)range;

NSFileManager

1
- (nullable NSDirectoryEnumerator<NSURL *> *)enumeratorAtURL:(NSURL *)url includingPropertiesForKeys:(nullable NSArray<NSURLResourceKey> *)keys options:(NSDirectoryEnumerationOptions)mask errorHandler:(nullable BOOL (^)(NSURL *url, NSError *error))handler

###NSIndexPath

1
- (void)getIndexes:(NSUInteger *)indexes range:(NSRange)positionRang

###NSJSONSerialization

1
+ (NSData *)dataWithJSONObject:(id)obj options:(NSJSONWritingOptions)opt error:(NSError **)error

NSDictionary

hook 方法:

1
2
3
+ (id)sharedKeySetForKeys:(NSArray<KeyType <NSCopying>> *)keys
- (instancetype)initWithObjects:(const ObjectType _Nonnull [_Nullable])objects forKeys:(const KeyType <NSCopying> _Nonnull [_Nullable])keys
- (instancetype)initWithObjects:(const ObjectType _Nonnull [_Nullable])objects forKeys:(const KeyType <NSCopying> _Nonnull [_Nullable])keys count:(NSUInteger)cnt

NSMutableDictionary

hook 方法:

1
2
3
+ (NSMutableDictionary<KeyType, ObjectType> *)dictionaryWithSharedKeySet:(id)keyset
- (void)setObject:(ObjectType)anObject forKey:(KeyType <NSCopying>)aKey;
- (void)removeObjectForKey:(KeyType)aKey;

###NSSet

1
2
3
4
5
- (instancetype)WT_initWithObjects:(const id [])objects count:(NSUInteger)cnt
- (void)addObject:(id)object;
- (void)makeObjectsPerformSelector:(SEL)aSelector
- (void)makeObjectsPerformSelector:(SEL)aSelector
withObject:(id)argument

###NSMutableSet

1
- (void)addObject:(id)anObject

###NSMutableString

1
2
3
4
5
6
7
8
9
- (void)setString:(NSString *)aString
- (void)appendString:(NSString *)aString
- (void)deleteCharactersInRange:(NSRange)range
- (void)insertString:(NSString *)aString atIndex:(NSUInteger)loc
- (void)replaceCharactersInRange:(NSRange)range withString:(NSString *)aString
- (NSUInteger)replaceOccurrencesOfString:(NSString *)target
withString:(NSString *)replacement
options:(NSStringCompareOptions)options
range:(NSRange)searchRange

###NSURL

1
2
3
4
5
6
7
8
9
10
11
12
13
+ (NSURL *)fileURLWithPath:(NSString *)path
+ (NSURL *)fileURLWithPath:(NSString *)path isDirectory:(BOOL)isDir
+ (NSURL *)fileURLWithPathComponents:(NSArray<NSString *> *)components
+ (NSURL *)fileURLWithPath:(NSString *)path
isDirectory:(BOOL)isDir
relativeToURL:(NSURL *)baseURL
- (instancetype)initWithString:(NSString *)URLString relativeToURL:(NSURL *)baseURL
- (instancetype)initFileURLWithPath:(NSString *)path
- (instancetype)initFileURLWithPath:(NSString *)path
relativeToURL:(NSURL *)baseURL
- (instancetype)initFileURLWithPath:(NSString *)path
isDirectory:(BOOL)isDir
relativeToURL:(NSURL *)baseURL

  1. KVO
  2. 容器越界(NSArray, NSDictionary,…)
  3. unrecognized selector crash (这个很多时候是由于class使用错误导致)
  4. NSTimer 导致crash

KVO Crash

项目中KVO crash 占比很高, 主要原因为,添加删除不对称导致。
解决方法为,添加Map进行缓存。
不过这个方案,目前还有缺陷。

unrecognized selector crash

这个就比较简单了,直接上代码:

1
2
3
4
5
6
7
8
9
10
11
    [NSObject jr_swizzleMethod:@selector(forwardingTargetForSelector:) withMethod:@selector(WT_safeForwardingTargetForSelector:) error:&error];

- (id)WT_safeForwardingTargetForSelector:(SEL)aSelector
{
NSMethodSignature *signature = [self methodSignatureForSelector:aSelector];
if ([self respondsToSelector:aSelector] || signature) {
return [self WT_safeForwardingTargetForSelector:aSelector];
}

return [WTSafeGuard createFakeForwardTargetObject:self selector:aSelector];
}

---------Thanks for your attention---------
0%